#

import logging
from collections import OrderedDict

LOGGER = logging.getLogger("wglmkapplayer")


# AST
#
class AST :


    # __getattr__
    #
    def __getattr__ (self, name) :
        gname = f"get_{name}"
        getter = self.__class__.__dict__[gname]
        return getter(self)


# ASTRegistry
#
class ASTRegistry (AST) :

    def __init__ (self) :
        self.type_decls = []
        self.enums = OrderedDict()
        self.functions = OrderedDict()

    def add_type_decl (self, tdecl) :
        self.type_decls.append(tdecl)

    def add_enum (self, enum) :
        assert enum.name not in self.enums, enum.name
        self.enums[enum.name] = enum
        
    def add_function (self, func) :
        assert func.name not in self.functions
        self.functions[func.name] = func


# ASTTypeDecl
#
class ASTTypeDecl (AST) :

    def __init__ (self, name, decl) :
        self.name = name
        self.decl = decl


# ASTTypeName
#
class ASTTypeName (AST) :

    NICKS = {
        '*'         : 'P',
        'const'     : 'C',
        'void'      : 'v',
        'GLvoid'    : 'V', # [FIXME] should not be used
        'GLenum'    : 'e',
        'GLbitfield': 't',
        'GLboolean' : 'o',
        'GLchar'    : 'c',
        'GLbyte'    : 'b',
        'GLubyte'   : 'y',
        'GLshort'   : 'h',
        'GLint'     : 'i',
        'GLintptr'  : 'ip',
        'GLuint'    : 'u',
        'GLuint64'  : 'u64',
        'GLsizei'   : 's',
        'GLsizeiptr': 'sp',
        'GLfixed'   : 'x',
        'GLfloat'   : 'f',
        'GLdouble'  : 'd',
        'GLclampf'  : 'cf',
        'GLclampd'  : 'cd',
    }

    # avoid doublons
    assert len(NICKS) == len(set(NICKS.values()))

    def __init__ (self, ptype, decl) :
        super().__init__()
        self.ptype = ptype
        self.decl = decl.strip()
        self.nick = self.make_nick(decl)

    def make_nick (self, decl) :
        # add spaces so * are split as words
        words = decl.replace('*', ' * ').split()
        return ''.join(ASTTypeName.NICKS.get(w, w) for w in words)

    def is_void (self) :
        return self.decl in ('void', 'GLvoid')


# ASTEnum
#
class ASTEnum (AST) :

    ENUM_ID = 0
    
    def __init__ (self, name, enum_type, group, values) :
        if name is None :
            ASTEnum.ENUM_ID += 1
            name = f"anonymous${ASTEnum.ENUM_ID}"
            self.anonymous = False
        else :
            self.anonymous = True
        self.name = name
        self.enum_type = enum_type
        assert isinstance(group, str)
        assert ',' not in group
        self.group = group
        self.values = tuple(values)


# ASTEnumValue
#
class ASTEnumValue (AST) :

    def __init__ (self, name, value_type, groups, value, alias, api) :
        self.name = name
        self.value_type = value_type
        # avoid passing str
        assert isinstance(groups, (tuple, list)), groups
        self.groups = tuple(groups)
        self.value = value
        self.alias = alias
        self.api = api


# ASTParam
#
class ASTParam (AST) :

    def __init__ (self, name, ptype) :
        self.name = name
        self.ptype = ptype


# ASTFunction
#
class ASTFunction (AST) :

    def __init__ (self, name, return_type, params, aliases) :
        self.name = name
        self.return_type = return_type
        self.params = tuple(params)
        self.aliases = tuple(aliases)

    def is_void (self) :
        return self.return_type.is_void()

    def get_params_type_seq (self) :
        return ', '.join(p.ptype.decl for p in self.params)

    def get_named_params_type_seq (self, prepend=None) :
        params = []
        if prepend :
            params.extend(prepend)
        params.extend(f"{p.ptype.decl} {p.name}" for p in self.params)
        return ', '.join(params)

    def get_params_names_seq (self, prepend=None) :
        params = []
        if prepend :
            params.extend(prepend)
        params.extend(f"{p.name}" for p in self.params)
        return ', '.join(params)

    def get_marshaller (self) :
        nicks = [self.return_type.nick]
        nicks.extend(p.ptype.nick for p in self.params)
        return 'marshaller_' + '_'.join(nicks)

    def get_pointer_type (self) :
        return f"{self.return_type.decl} (*) ( {self.params_type_seq} )"

    def get_prototype (self, name_prefix='', params_prepend=None) :
        # pylint: disable=line-too-long
        params = self.get_named_params_type_seq(prepend=params_prepend)
        return f"{self.return_type.decl} {name_prefix}{self.name} ( {params} )"
